import * as THREE from "../../../modules/three/three.module.js";
import {assets3dBaseUrl, KeyboardState, MouseButton, WorldConfig} from '../lib/CommonConsts.js';
import HoleMesh from '../lib/gadget/HoleMesh.js';
import {
  areaMtl,
  bufferAttributeToVectors,
  getPolygonGeometry,
  holeMtl, updatePosAttr,
} from '../lib/util/StaticMethods.js';
import World3dEvent from '../scene/event/World3dEvent.js';
import UndoType from '../scene/undo/UndoType.js';
import PluginBase from './PluginBase.js';
import {removeArrayItem, replaceArrayItem} from "../lib/util/ToolMethods.js";
import WorkAreaMesh from "../lib/gadget/WorkAreaMesh.js";

const dv =
  WorldConfig.adhereRadiusForEditing * WorldConfig.adhereRadiusForEditing;
const de = WorldConfig.adhereEdgeDistance * WorldConfig.adhereEdgeDistance;

// 编辑顶点时，鼠标光标吸附到顶点时，鼠标光标对应的3d坐标
const editVertStart = new THREE.Vector3();
const pointOnPlane = new THREE.Vector3();

const spCircle = new THREE.TextureLoader().load(assets3dBaseUrl + 'texture/circle.png');
const pSelectMat = new THREE.PointsMaterial({
  size: 16,
  sizeAttenuation: false,
  map: spCircle,
  alphaTest: 0.5,
  depthTest: false,
  depthWrite: false,
  transparent: true,
});

const pHoverMat = new THREE.PointsMaterial({
  size: 12,
  sizeAttenuation: false,
  map: spCircle,
  color: '#0f0',
  alphaTest: 0.5,
  depthTest: false,
  depthWrite: false,
  transparent: true,
});

const spCross = new THREE.TextureLoader().load(assets3dBaseUrl + 'texture/cross.png');
const pCrossMat = new THREE.PointsMaterial({
  size: 24,
  sizeAttenuation: false,
  map: spCross,
  color: '#0ff',
  alphaTest: 0.5,
  depthTest: false,
  depthWrite: false,
  transparent: true,
});

const spDisc = new THREE.TextureLoader().load(assets3dBaseUrl + 'texture/disc.png');
const pointsMaterial = new THREE.PointsMaterial({
  size: 12,
  sizeAttenuation: false,
  color: '#005dbb',
  map: spDisc,
  alphaTest: 0.5,
  depthTest: false,
  depthWrite: false,
  transparent: true,
});

const pointGeo = new THREE.BufferGeometry();
pointGeo.setAttribute(
  'position',
  new THREE.BufferAttribute(new Float32Array([0, 0, 0]), 3),
);
const hoverEdgePointer = new THREE.Points(pointGeo, pHoverMat);
const hoverVertexPointer = new THREE.Points(pointGeo, pSelectMat);
hoverEdgePointer.renderOrder = WorldConfig.order_vert_edit;
hoverVertexPointer.renderOrder = WorldConfig.order_vert_hover;

const crossSetGeo = new THREE.BufferGeometry();
crossSetGeo.setAttribute(
  'position',
  new THREE.BufferAttribute(new Float32Array(new Array(27).fill(0)), 3),
);
const crossSetPointer = new THREE.Points(crossSetGeo, pCrossMat);
crossSetPointer.renderOrder = WorldConfig.order_vert_cross;
const crossSingleGeo = new THREE.BufferGeometry();
crossSingleGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(new Array(3).fill(0)), 3),);
const crossSinglePointer = new THREE.Points(crossSingleGeo, pCrossMat.clone());
crossSinglePointer.material.color.setStyle('#d00')
crossSinglePointer.renderOrder = WorldConfig.order_vert_cross;

class AdhereItem {
  // 吸附到的固有顶点
  ctrlPoint = new THREE.Vector3();
  // 吸附到边上的新顶点
  posOnEdge = new THREE.Vector3();
  // 关于 this._lines 边索引 或 关于 this._vectors 顶点索引
  index = -1;
  onPoint = false;
  onEdge = false;
}

const adhereItem = new AdhereItem();

// true 表示鼠标左键正在被按下
let leftMousePressing = false;

// 修改 不可喷涂区域孔洞的顶点插件
export default class ModifyVertexPlugin extends PluginBase {
  /**
   * @type {THREE.Vector3[]}
   * @private
   */
  _vectors = [];
  /**
   * @type {THREE.Line3[]}
   * @private
   */
  _lines = [];
  /**
   * @type {WorkArea}
   * @private
   */
  _workArea;
  _plane;
  /**
   * @type {AdhereItem|null}
   * @private
   */
  _currentAdhere;
  /**
   * @type {THREE.Points}
   * @private
   */
  _vertexParticles;
  world3d;

  constructor(world3d) {
    super();
    this.world3d = world3d;
  }

  /**
   * 初始化 this._vertexParticles
   * @param {HoleMesh} workArea
   * @private
   */
  attachItem(workAreaMesh) {
    const workArea = workAreaMesh.vo
    if (this._workArea === workArea) {
      return;
    }
    this._workArea = workArea;
    this._plane = workArea.createPlane();
    const geometry = workAreaMesh.geometry;
    this._vertexParticles = new THREE.Points(geometry, pointsMaterial);
    this._vertexParticles.renderOrder = WorldConfig.order_vert_edit;
    this.world3d.scene.add(this._vertexParticles);
    this._vectors = bufferAttributeToVectors(geometry.getAttribute('position'));
    this._updateLines();
  }

  detachItem() {
    if (!this._workArea) {
      return;
    }
    hoverVertexPointer.removeFromParent();
    hoverEdgePointer.removeFromParent();
    adhereItem.onPoint = false;
    adhereItem.onEdge = false;
    this._vertexParticles.removeFromParent();
    this._vectors.length = 0;
    this._lines.length = 0;
    this._workArea = null;
    this._currentAdhere = null;
  }

  replaceCurrentHoleOrArea() {
    const store = this.world3d.store;
    const pickingMesh = store.pickingHole() || store.pickingArea();
    let newHoleMesh
    let removingPoint = false
    if (this._currentAdhere.onEdge) {
      // 在孔洞上添加顶点
      newHoleMesh = this._newHoleMeshByVertAdd(adhereItem.index, adhereItem.posOnEdge);
    } else if (this._currentAdhere.onPoint && KeyboardState.keyDownAlt) {
      // 在孔洞上删除顶点
      removingPoint = true
      newHoleMesh = this._newHoleMeshByVertRemove(adhereItem.index);
    }
    if (!newHoleMesh) {
      return
    }
    const ctx = {
      scene: this.world3d.scene,
      removedArea: [],
      addedArea: [],
      arrArea: store.areaMeshes,
      arrHole: store.holeMeshes,
      oldHole: pickingMesh,
      newHole: newHoleMesh,
    };
    ctx.callback = (oldMesh) => {
      store.resetPicking(oldMesh);
      this.detachItem()
      oldMesh.backupShape()
      this.attachItem(oldMesh)
    };
    // 更新相关工作区
    if (store.pickingHole() && removingPoint) {
      const obj = this.world3d.editHoleArea.updateWorkAreaByHole(newHoleMesh, false);
      if (obj) {
        ctx.removedArea = obj.removed;
        ctx.addedArea = obj.added;
      }
    }
    this.world3d.editRecord.addAction(UndoType.replaceHole, ctx);

    if (store.pickingHole()) {
      replaceArrayItem(store.holeMeshes, newHoleMesh, pickingMesh)
    } else {
      replaceArrayItem(store.areaMeshes, newHoleMesh, pickingMesh)
    }
    this.world3d.scene.add(newHoleMesh);
    pickingMesh.removeFromParent();
    newHoleMesh.backupShape()
    this.detachItem();
    this.attachItem(newHoleMesh);
    store.resetPicking(newHoleMesh);
  }

  _updateLines() {
    this._lines.length = 0
    const segments = this._workArea.initSegments()
    const points = this._vectors
    for (let i = 0; i < segments.length; i += 2) {
      const vector = points[segments[i]]
      const nextP = points[segments[i + 1]]
      const line3 = new THREE.Line3(vector, nextP)
      this._lines.push(line3)
    }
  }

  /**
   * 用新增了顶点的孔洞，替换原来的孔洞；因为新增的顶点始终在边上，不可能有形状改变，所以不用更新相关的工作区
   * @param {number} lineIndex
   * @param {THREE.Vector3} posOnEdge
   * @return {HoleMesh|WorkAreaMesh}
   * @private
   */
  _newHoleMeshByVertAdd(lineIndex, posOnEdge) {
    const workArea = this._workArea;
    const planeMatInv = workArea.planeMatInv;
    const contour = [];
    this._lines.forEach((line3, index) => {
      contour.push(line3.start.clone().applyMatrix4(planeMatInv));
      if (index === lineIndex) {
        contour.push(posOnEdge.clone().applyMatrix4(planeMatInv));
      }
    });
    const newWorkArea = workArea.cloneArea();
    newWorkArea.updateHoleIndicesByVertAdd(lineIndex)
    newWorkArea.verts = [contour, newWorkArea.verts[1]];

    const geometry = getPolygonGeometry(
      newWorkArea.verts,
      newWorkArea.planeMat,
      newWorkArea.facing,
    );
    let newMesh
    if (workArea.isHole) {
      newMesh = new HoleMesh(geometry, holeMtl.clone())
    } else {
      newMesh = new WorkAreaMesh(geometry, areaMtl.clone())
    }
    newMesh.vo = newWorkArea;
    return newMesh;
  }

  /**
   * 用删除了顶点的孔洞，替换原来的孔洞；大概率会发生形变（矩形变三角形），之后需要更新相关工作区
   * @param {number} pointIndex
   * @return {HoleMesh|null}
   * @private
   */
  _newHoleMeshByVertRemove(pointIndex) {
    if (this._vectors.length < 4) {
      // 三角形的图形只有三个顶点，不能继续删除顶点了
      console.warn('只有三个顶点，不能继续删除顶点了')
      return null;
    }
    const workArea = this._workArea;
    const newWorkArea = workArea.cloneArea();
    newWorkArea.updateHoleIndicesByVertRemove(pointIndex)
    if (!newWorkArea.verts) {
      console.warn('最外轮廓不足三个顶点，不能继续删除顶点了')
      return null;
    }
    newWorkArea.updateLocalBox();
    const geometry = getPolygonGeometry(
      newWorkArea.verts,
      newWorkArea.planeMat,
      newWorkArea.facing,
    );
    let newMesh
    if (workArea.isHole) {
      newMesh = new HoleMesh(geometry, holeMtl.clone())
    } else {
      newMesh = new WorkAreaMesh(geometry, areaMtl.clone())
    }
    newMesh.vo = newWorkArea;
    return newMesh;
  }

  /**
   * 鼠标按下
   * @param {PointerEvent} event
   */
  pointerDown(event) {
    const store = this.world3d.store;
    if (event.button === MouseButton.right) {
      // mouse right button down
      return;
    }
    leftMousePressing = true;

  }

  pointerMove(event) {
    if (event.button === 2) {
      // mouse right button down
      return;
    }
    const store = this.world3d.store;
    const scene = this.world3d.scene;
    const holeOrAreaMesh = store.pickingHole() || store.pickingArea();
    if (!store.stateEditingHole || !holeOrAreaMesh || !this._workArea) {
      return;
    }
    this.world3d.mouseRaycaster.ray.intersectPlane(this._plane, pointOnPlane);
    const curAdhere = this._currentAdhere;
    if (
      curAdhere &&
      curAdhere.ctrlPoint &&
      leftMousePressing &&
      !curAdhere.onEdge
    ) {
      const offset = pointOnPlane.clone().sub(editVertStart);
      offset.add(curAdhere.ctrlPoint);
      updatePosAttr(holeOrAreaMesh.geometry, curAdhere.index, offset)
      updatePosAttr(this._vertexParticles.geometry, curAdhere.index, offset)
      hoverVertexPointer.position.copy(offset);
      this._vectors[curAdhere.index].copy(offset);
      const crossPoints = holeOrAreaMesh.getSelfIntersectPoints();
      if (crossPoints.length) {
        let points = crossPoints.map((it) =>
          new THREE.Vector3(it.x, it.y, 0).applyMatrix4(
            this._workArea.planeMat,
          ),
        );
        while (points.length < 9) {
          points.unshift(points[0]);
        }
        points = points.slice(0, 9);
        const crossAttr = crossSetPointer.geometry.getAttribute('position');
        points.forEach((it, index) => {
          crossAttr.setXYZ(index, it.x, it.y, it.z);
        });
        crossAttr.needsUpdate = true;
        this.world3d.scene.add(crossSetPointer);
      } else {
        crossSetPointer.removeFromParent();
      }
      holeOrAreaMesh.updateModelData()
    } else {
      // 判定是吸附到顶点 还是吸附的边 还是没有吸附
      hoverVertexPointer.removeFromParent();
      hoverEdgePointer.removeFromParent();
      crossSinglePointer.removeFromParent();
      adhereItem.onPoint = false;
      adhereItem.onEdge = false;
      for (let i = 0; i < this._vectors.length; i++) {
        const vector = this._vectors[i];
        const distanceSq = vector.distanceToSquared(pointOnPlane);
        if (distanceSq <= dv) {
          adhereItem.ctrlPoint.copy(vector);
          adhereItem.index = i;
          this._currentAdhere = adhereItem;
          this.world3d.scene.add(hoverVertexPointer);
          hoverVertexPointer.position.copy(adhereItem.ctrlPoint);
          adhereItem.onPoint = true;
          editVertStart.copy(pointOnPlane);
          if (KeyboardState.keyDownAlt) {
            scene.add(crossSinglePointer)
            crossSinglePointer.position.copy(adhereItem.ctrlPoint)
          }
          return;
        }
      }
      for (let i = 0; i < this._lines.length; i++) {
        const line = this._lines[i];
        const t = line.closestPointToPointParameter(pointOnPlane, false);
        if (t > 1 || t < 0) {
          continue;
        }
        const posOnEdge = line.closestPointToPoint(
          pointOnPlane,
          false,
          adhereItem.posOnEdge,
        );
        const distanceSq = posOnEdge.distanceToSquared(pointOnPlane);
        if (distanceSq <= de) {
          adhereItem.posOnEdge = posOnEdge;
          adhereItem.index = i;
          this._currentAdhere = adhereItem;
          this.world3d.scene.add(hoverEdgePointer);
          hoverEdgePointer.position.copy(adhereItem.posOnEdge);
          adhereItem.onEdge = true;
          return;
        }
      }
      this._currentAdhere = null;
    }
  }

  pointerUpMoved(event) {
    leftMousePressing = false;
    if (event.button === MouseButton.right) {
      // mouse right button down
      return;
    }
    const store = this.world3d.store;
    const pickingMesh = store.pickingHole() || store.pickingArea();
    if (
      !store.stateEditingHole ||
      !pickingMesh ||
      !this._currentAdhere ||
      !this._currentAdhere.onPoint
    ) {
      return;
    }

    if (this._currentAdhere && this._currentAdhere.onPoint) {
      const selfIntersects = pickingMesh.getSelfIntersectPoints();
      if (selfIntersects.length) {
        // 不允许图形自相交
        pickingMesh.restoreShapeFromBackup();
        const posAttr = this._vertexParticles.geometry.getAttribute('position');
        posAttr.copy(pickingMesh.geometry.getAttribute('position'));
        posAttr.needsUpdate = true;
        hoverVertexPointer.removeFromParent();
        crossSetPointer.removeFromParent();
        this._vectors[this._currentAdhere.index].fromBufferAttribute(
          posAttr,
          this._currentAdhere.index,
        );
        this.world3d.scene.dispatchEvent(World3dEvent.contourSelfIntersected);
        return;
      }
    }

    // pickingMesh.updateModelData();
    const posAttr = this._vertexParticles.geometry.getAttribute('position');
    posAttr.copy(pickingMesh.geometry.getAttribute('position'));
    posAttr.needsUpdate = true;

    if (!pickingMesh.vo.isHole) {
      removeArrayItem(store.areaMeshes, pickingMesh)
      store.addingAreaMesh = pickingMesh
      const ctx = this.world3d.editWorkArea.addNewWorkArea()
      const {added, removed} = ctx
      if (added.length === 1 && added[0] !== pickingMesh) {
        added[0].backupShape()
        store.resetPicking(added[0])
        this.detachItem()
        this.attachItem(added[0])
        pickingMesh.restoreShapeFromBackup()
        removed.push(pickingMesh)
        ctx.callback = (meshes) => {
          this.detachItem()
          meshes[0].backupShape();
          this.attachItem(meshes[0]);
        }
        return
      }
      if (added.length === 1 && !removed.length) {
        this.world3d.editRecord.stackUndo.pop()
      } else if (added.length > 1) {
        this.detachItem()
        return
      }
    }
    const num = this._currentAdhere.index;
    const ctx = {
      scene: this.world3d.scene,
      removedArea: [],
      addedArea: [],
      arrArea: store.areaMeshes,
      holeMesh: pickingMesh,
      index: num,
      oldPos: this._currentAdhere.ctrlPoint.clone(),
      newPos: this._vectors[num].clone(),
      adherePos: this._vectors[num],
      posAttr,
    };
    if (pickingMesh.vo.isHole) {
      const obj = this.world3d.editHoleArea.updateWorkAreaByHole(pickingMesh, false);
      if (obj) {
        ctx.removedArea = obj.removed;
        ctx.addedArea = obj.added;
      }
    }
    this.world3d.editRecord.addAction(UndoType.shapeHole, ctx);
  }

  pointerUpUnmoved(event) {
    leftMousePressing = false;
    const store = this.world3d.store;
    if (event.button === 2) {
      // mouse right button down
      if (store.stateEditingHole) {
        this.detachItem();
        store.freeState();
        this.world3d.switchCamera(false);
      }
      return;
    }
    if (!store.stateEditingHole) {
      return;
    }
    const holeOrAreaMesh = store.pickingHole() || store.pickingArea();
    if (holeOrAreaMesh) {
      if (this._currentAdhere) {
        this.replaceCurrentHoleOrArea()
      } else {
        this.detachItem()
        holeOrAreaMesh.backupShape();
        this.attachItem(holeOrAreaMesh);
      }
    } else {
      this.detachItem()
    }
  }
}
